﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Net;
using AMIUtilities;
using System.Threading;

//    Copyright (C) 2011 Officebroker.com LTD.
//    Neil Cadman, Alex Welding, Andrew Coombs, Jan Rygielski
//    Sir Robert Peel Mill, Mill Lane, Staffordshire, Fazely, B78 3QD, England
//
//    This file is part of AMIProxy Project.
//
//    AMIProxy Project is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    AMIProxy Project is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License (GNU AGPL)
//    along with AMIProxy Project.  If not, see <http://www.gnu.org/licenses/>.

namespace PhoneClientLogic
{

    public enum PhoneType
    {
        AASTRA6757i
    }

    public class PhoneClient
    {
        // Private Variables
        private TcpClient _tcpClient;
        private ManagedSocket _managedSocket;
        private List<Phone> _phones = new List<Phone>();
        private List<Channel> _channels = new List<Channel>();
        private List<String> _pbxSites = new List<String>();
        private PhoneChangedDelegate _phoneChanged;
        private PhoneChangedDelegate _phoneAdded;
        private ChannelChangedDelegate _channelUpdated;
        private ChannelAddedDelegate _channelAdded;
        private ChannelChangedDelegate _channelRemoved;
        private WebsiteErrorDelegate _websiteError;
        private PhoneType _phoneType;

        private String command = "";

        private const String ENDLINEORNOTHING = "(|$)";
        private const String ANYTHINGANDSPACES = "([ \\S ]*)";
        private const String CHANNELIDPATTERN = "(ChannelID: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String STATUSIDPATTERN = "(StatusID: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String STATUSDESCPATTERN = "(StatusDesc: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String DIALEDPATTERN = "(Dialed: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String EXTENSIONPATTERN = "(Extension: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String ACTIONPATTERN = "(Action: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String IPPATTERN = "(IP: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String PORTPATTERN = "(Port: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String STATUSPATTERN = "(Status: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String TYPEPATTERN = "(Type: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String BRIDGEDTOIDPATTERN = "(BridgedToID: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String BRIDGEDTONAMEPATTERN = "(BridgedToName: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String BRIDGEDFROMIDPATTERN = "(BridgedFromID: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String BRIDGEDFROMNAMEPATTERN = "(BridgedFromName: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String CALLEDFROMPATTERN = "(CalledFrom: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String CALLEDFROMNAMEPATTERN = "(CalledFromName: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String CALLEDTOPATTERN = "(CalledTo: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String CALLEDTONAMEPATTERN = "(CalledToName: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String TARGETCHANNELIDPATTERN = "(ChannelIDTarget: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String ONHOLDPATTERN = "(OnHold: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;
        private const String PLACEDONHOLDPATTERN = "(OnHoldPlaced: )" + ANYTHINGANDSPACES + ENDLINEORNOTHING;

        // Public Delegates
        public delegate void PhoneChangedDelegate(Phone phone);
        public delegate void ChannelChangedDelegate(Channel channel);
        public delegate void ChannelAddedDelegate(ChannelEventArgs args);
        public delegate void WebsiteErrorDelegate(String error);

        // Private Delegates
        private delegate void readSocketDelegate(Socket socket);

        // Public Events
        public event PhoneChangedDelegate PhoneChanged
        {
            add { _phoneChanged += value; }
            remove { _phoneChanged -= value; }
        }
        public event PhoneChangedDelegate PhoneAdded
        {
            add { _phoneAdded += value; }
            remove { _phoneAdded -= value; }
        }
        public event ChannelChangedDelegate ChannelUpdated
        {
            add { _channelUpdated += value; }
            remove { _channelUpdated -= value; }
        }
        public event ChannelAddedDelegate ChannelAdded
        {
            add { _channelAdded += value; }
            remove { _channelAdded -= value; }
        }
        public event ChannelChangedDelegate ChannelRemoved
        {
            add { _channelRemoved += value; }
            remove { _channelRemoved -= value; }
        }
        public event WebsiteErrorDelegate WebsiteError
        {
            add { _websiteError += value; }
            remove { _websiteError -= value; }
        }
        
        // Public Methods
        public void InitiateConnection(String hostName, Int32 port, List<String> pxbSites, PhoneType phoneType)
        {
            _pbxSites = pxbSites;
            _phoneType = phoneType;

            _tcpClient = new TcpClient(hostName, port);

            _managedSocket = new ManagedSocket(RandomFactory.Create().Next(1000000).ToString(), _tcpClient.Client);
            _managedSocket.DataReceived += new ManagedSocket.dataReceivedDelegate(_managedSocket_DataReceived);
            _managedSocket.ReadStart();
        }

        public void ShutDown()
        {
            if (_managedSocket != null)
                _managedSocket.Close();

            if (_tcpClient != null)
                _tcpClient.Close();
        }
        public virtual void newMessage(String message)
        { }
        public void SendMessageToAMI(String message)
        {
            _managedSocket.SendMessage(message);
        }
        public void SendMessageToPhone(String querystring, String phoneIP)
        {
            if (querystring.StartsWith("&"))
                querystring = querystring.TrimStart("&".ToCharArray());

            foreach (String site in _pbxSites)
            {
                String url = site;

                switch (_phoneType)
                {
                    case PhoneType.AASTRA6757i :
                        url += "6757i.aspx?Push=1&IP=" + phoneIP + "&" + querystring;
                        break;
                }

                SendWebRequest(url);

                return;
            }

            OnWebsiteError("Dial failed. Please dial from your handset or try again later.");

            //return statusCode;
        }

        // Private Methods
        private void HandleMessage(String message)
        {

            Match actionMatch;

            actionMatch = Regex.Match(message, ACTIONPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);

            if(actionMatch.Success)
                if (actionMatch.Result("$2") == "NewPhone" || actionMatch.Result("$2") == "PhoneUpdated")
                    NewPhoneMessage(message, actionMatch.Result("$2"));
                else if (actionMatch.Result("$2") == "ChannelMessage")
                    ChannelMessage(message);

            newMessage(message);

        }
        private void NewPhoneMessage(String message, String type)
        {

            String Extension;
            String IP;
            String tempPort;
            String Status;
            Int32 Port;

            Extension = Regex.Match(message, EXTENSIONPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            IP = Regex.Match(message, IPPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            tempPort = Regex.Match(message, PORTPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            Status = Regex.Match(message, STATUSPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            Int32.TryParse(tempPort, out Port);

            if (type == "NewPhone")
            {
                Phone newPhone;
                newPhone = new Phone(Extension, IP, Port, Status);
                _phones.Add(newPhone);
                OnPhoneAdded(newPhone);
            }
            else
            {

                foreach (Phone phone in _phones)
                    if (phone.Extension == Extension)
                    {
                        phone.IP = IP;
                        phone.Port = Port;
                        phone.CurrentStatus = Status;
                        OnPhoneChanged(phone);
                        break;
                    }
            }

        }
        private void ChannelMessage(String message)
        {

            String type;
            String extension;
            String dialed = "";
            String channelID;
            String statusDesc;
            String calledFrom = "";
            String calledFromName = "";
            String targetChannelID = "";
            String onHold = "";
            String placedOnHold = "";
            Match dialedMatch;
            Match calledFromMatch;
            Match calledFromNameMatch;
            Match targetChannelIDMatch;
            Match onHoldMatch;
            Match placedOnHoldMatch;

            type = Regex.Match(message, TYPEPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            extension = Regex.Match(message, EXTENSIONPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            channelID = Regex.Match(message, CHANNELIDPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");
            statusDesc = Regex.Match(message, STATUSDESCPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline).Result("$2");

            calledFromMatch = Regex.Match(message, CALLEDFROMPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
            if(calledFromMatch.Success)
                calledFrom = calledFromMatch.Result("$2");

            calledFromNameMatch = Regex.Match(message, CALLEDFROMNAMEPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
            if (calledFromNameMatch.Success)
                calledFromName = calledFromNameMatch.Result("$2");

            targetChannelIDMatch = Regex.Match(message, TARGETCHANNELIDPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
            if (targetChannelIDMatch.Success)
                targetChannelID = targetChannelIDMatch.Result("$2");

            dialedMatch = Regex.Match(message, DIALEDPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
            if (dialedMatch.Success)
                dialed = dialedMatch.Result("$2");

            onHoldMatch = Regex.Match(message, ONHOLDPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
            if (onHoldMatch.Success)
                onHold = onHoldMatch.Result("$2");

            placedOnHoldMatch = Regex.Match(message, PLACEDONHOLDPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
            if (placedOnHoldMatch.Success)
                placedOnHold = placedOnHoldMatch.Result("$2");

            if (type == "Added" || type == "Cloned")
            {
                Channel newChannel;
                newChannel = new Channel(channelID, extension, dialed, calledFrom, calledFromName, statusDesc);

                lock (_channels)
                {
                    foreach (Phone phone in _phones)
                        if (phone.Extension == extension)
                        {
                            phone.Channels.Add(newChannel);
                            _channels.Add(newChannel);
                            break;
                        }
                }

                if (type == "Cloned")
                {
                    lock (_channels)
                    {
                        foreach (Channel channel in _channels)
                            if (channel.ID == targetChannelID)
                            {
                                OnChannelAdded(new ChannelEventArgs(newChannel, channel));
                                break;
                            }
                    }

                    calledFromMatch = Regex.Match(message, CALLEDTOPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
                    if (calledFromMatch.Success)
                        calledFrom = calledFromMatch.Result("$2");

                    calledFromNameMatch = Regex.Match(message, CALLEDTONAMEPATTERN, RegexOptions.IgnoreCase & RegexOptions.Multiline);
                    if (calledFromNameMatch.Success)
                        calledFromName = calledFromNameMatch.Result("$2");

                    newChannel.CalledFrom = calledFrom;
                    newChannel.CalledFromName = calledFromName;

                    newChannel.OnChannelUpdated(newChannel);

                    return;
                }

                OnChannelAdded(new ChannelEventArgs(newChannel, null));
            }
            else if (type == "Update")
            {
                lock (_channels)
                {
                    foreach (Channel channel in _channels)
                        //if (channel.Extension == extension)
                        if (channel.ID == channelID)
                        {
                            channel.Number = dialed;
                            channel.StateDescription = statusDesc;
                            channel.CalledFrom = calledFrom;
                            channel.CalledFromName = calledFromName;
                            channel.OnHold = Boolean.Parse(onHold);
                            channel.PlacedOnHold = Boolean.Parse(placedOnHold);
                            channel.OnChannelUpdated(channel);
                            OnChannelUpdated(channel);
                            break;
                        }
                }
            }
            else
            {
                Channel tempChannel = null;
                lock (_channels)
                {
                    foreach (Channel channel in _channels)
                        if (channel.ID == channelID)
                        {
                            tempChannel = channel;
                            break;
                        }

                    if (tempChannel != null)
                    {
                        _channels.Remove(tempChannel);
                        tempChannel.OnChannelRemoved(tempChannel);
                        OnChannelRemoved(tempChannel);
                    }
                }
            }

        }

        private void OnPhoneAdded(Phone phone)
        {
            if (_phoneAdded != null)
                _phoneAdded.RaiseEventSafely(new Object[] {phone});
        }
        private void OnPhoneChanged(Phone phone)
        {
            if (_phoneChanged != null)
                _phoneChanged.RaiseEventSafely(new Object[] { phone });
        }
        private void OnChannelAdded(ChannelEventArgs args)
        {
            if (_channelAdded != null)
                _channelAdded.RaiseEventSafely(new Object[] { args });
        }
        private void OnChannelUpdated(Channel channel)
        {
            if (_channelUpdated != null)
                _channelUpdated.RaiseEventSafely(new Object[] { channel });
        }
        private void OnChannelRemoved(Channel channel)
        {
            if (_channelRemoved != null)
                _channelRemoved.RaiseEventSafely(new Object[] { channel });
        }
        private void OnWebsiteError(String error)
        {
            if (_websiteError != null)
                _websiteError.RaiseEventSafely(new Object[] { error });
        }

        private void SendWebRequest(String url)
        {
            WebRequest request = HttpWebRequest.Create(url);
            
            IAsyncResult result = request.BeginGetResponse(new AsyncCallback(AsyncCallback), request);
        }
        private void AsyncCallback(IAsyncResult ar)
        {
                WebRequest request = (WebRequest)ar.AsyncState;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);
        }

        private void _managedSocket_DataReceived(ManagedSocket socket, Byte[] data)
        {
            String[] lines;

            command += Encoding.ASCII.GetString(data, 0, data.Length);

            lines = Regex.Split(command, "\\r\\n\\r\\n", RegexOptions.Multiline);

            for (Int32 i = 0; i < lines.Length - 1; i++)
            {
                HandleMessage(lines[i]);
            }

            command = lines[lines.Length - 1];
        }

        // Public Classes
        public class Phone
        {

            // Private variables
            private String _extension;
            private String _IP;
            private Int32 _port;
            private String _currentStatus;
            private List<Channel> _channels = new List<Channel>();

            // Public Properties
            public String Extension
            {
                get { return _extension; }
                set
                {
                    _extension = value;
                }
            }
            public String IP
            {
                get { return _IP; }
                set
                {
                    _IP = value;
                }
            }
            public Int32 Port
            {
                get { return _port; }
                set
                {
                    _port = value;
                }
            }
            public String CurrentStatus
            {
                get { return _currentStatus; }
                set
                {
                    _currentStatus = value;
                }
            }
            public List<Channel> Channels
            {
                get { return _channels;}
            }

            // Public Constructors
            public Phone(String extension, String ip, Int32 port, String currentStatus)
            {
                _extension = extension;
                _IP = ip;
                _port = port;
                _currentStatus = currentStatus;
            }

        }
        public class Channel
        {

            // Private Variables
            private String _stateDescription;
            private String _number;
            private String _extension; 
            private String _id;
            private String _calledFrom;
            private String _calledFromName;
            private Boolean _onHold = false;
            private Boolean _placedOnHold = false;

            private ChannelChangedDelegate _channelUpdated;
            private ChannelRemovedDelegate _channelRemoved;

            // Public Properties
            public String StateDescription
            {
                get { return _stateDescription; }
                internal set { _stateDescription = value; }
            }
            public String Number
            {
                get { return _number; }
                internal set { _number = value; }
            }
            public String Extension
            {
                get { return _extension; }
            }
            public String ID
            {
                get { return _id; }
            }
            public String CalledFrom
            {
                get { return _calledFrom; }
                internal set { _calledFrom = value; }
            }
            public String CalledFromName
            {
                get { return _calledFromName; }
                internal set { _calledFromName = value; }
            }
            public Boolean OnHold
            {
                get { return _onHold; }
                internal set { _onHold = value; }
            }
            public Boolean PlacedOnHold
            {
                get { return _placedOnHold; }
                internal set { _placedOnHold = value; }
            }

            // Public Delegates
            public delegate void ChannelChangedDelegate(Channel channel);
            public delegate void ChannelRemovedDelegate(Channel channel);
            public delegate void ChannelClonedDelegate(Channel channel);

            // Public Events
            public event ChannelChangedDelegate ChannelUpdated
            {
                add { _channelUpdated += value; }
                remove { _channelUpdated -= value; }
            }
            public event ChannelRemovedDelegate ChannelRemoved
            {
                add { _channelRemoved += value; }
                remove { _channelRemoved -= value; }
            }

            // Public Constructors
            public Channel(String ID, String extension, String number, String calledFrom, String calledFromName, String stateDescription)
            {
                _id = ID;
                _extension = extension;
                _number = number;
                _calledFrom = calledFrom;
                _calledFromName = calledFromName;
                _stateDescription = stateDescription;
            }

            public void OnChannelUpdated(Channel channel)
            {
                if (_channelUpdated != null)
                    _channelUpdated.RaiseEventSafely(new Object[] { channel });
            }

            public void OnChannelRemoved(Channel channel)
            {
                if (_channelRemoved != null)
                    _channelRemoved.RaiseEventSafely(new Object[] { channel });
            }
        }

        // Public Structures
        public struct ChannelEventArgs
        {
            private Channel _newChannel;
            private Channel _targetChannel;

            public Channel NewChannel
            {
                get { return _newChannel; }
                set { _newChannel = value; }
            }
            public Channel TargetChannel
            {
                get { return _targetChannel; }
                set { _targetChannel = value; }
            }

            public ChannelEventArgs(Channel newChannel, Channel targetChannel)
            {
                _newChannel = newChannel;
                _targetChannel = targetChannel;
            }
        }
        
    }
}
